package de.fhffm.scs.simulator.body;

import static de.fhffm.scs.simulator.data.Constants.BSL_INCREASE_DURATION_PER_MG_IN_MINUTES;
import static de.fhffm.scs.simulator.data.Constants.BSL_INCREASE_LATENCY_IN_MINUTES;
import static de.fhffm.scs.simulator.data.Constants.BSL_INCREASE_PER_MG;
import static de.fhffm.scs.simulator.data.Constants.INSULIN_AFFECT_DURATION_PER_UNIT_IN_MINUTES;
import static de.fhffm.scs.simulator.data.Constants.INSULIN_AFFECT_LATENCY_IN_MINUTES;
import static de.fhffm.scs.simulator.data.Constants.INSULIN_AFFECT_PER_UNIT;
import static java.util.concurrent.TimeUnit.MINUTES;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import de.fhffm.scs.simulator.data.BloodSugarLevel;
import de.fhffm.scs.simulator.food.Aliment;
import de.fhffm.scs.simulator.scheduler.Scheduler;

public class SimulationBody extends Thread implements Body {

    private AtomicInteger currentBSL;

    private Scheduler scheduler;

    private List<Thread> runningThreads = new CopyOnWriteArrayList<Thread>();

    private enum Mode {
        INCREASE, DECREASE;
    }

    private class IncreaseDecrease extends Thread {

        private Mode mode;

        protected int initialLatencyInMinutes;

        protected int steps;

        protected int stepDurationInMinutes;

        protected int increaseDecreasePerStep;

        private IncreaseDecrease(Mode mode) {
            this.mode = mode;
        }

        @Override
        public void run() {
            setName(this.getClass().getSimpleName());
            runningThreads.add(this);
            int adjustedSteps = steps * increaseDecreasePerStep;
            int adjustedStepDurationInMinutes = stepDurationInMinutes / increaseDecreasePerStep;
            try {
                if (initialLatencyInMinutes != 0) {
                    scheduler.sleep(MINUTES, initialLatencyInMinutes);
                }
                for (int i = 0; i < adjustedSteps; i++) {
                    scheduler.sleep(MINUTES, adjustedStepDurationInMinutes);
                    if (Mode.INCREASE == mode) {
                        currentBSL.incrementAndGet();
                    } else if (Mode.DECREASE == mode) {
                        currentBSL.decrementAndGet();
                    }
                }
            } catch (InterruptedException e) {
                // Nothing to do
            }
            runningThreads.remove(this);
        }
    }

    private class ApplySugar extends IncreaseDecrease {

        private ApplySugar(int sugarInMg) {
            super(Mode.INCREASE);
            this.initialLatencyInMinutes = BSL_INCREASE_LATENCY_IN_MINUTES;
            this.steps = sugarInMg;
            this.stepDurationInMinutes = BSL_INCREASE_DURATION_PER_MG_IN_MINUTES;
            this.increaseDecreasePerStep = BSL_INCREASE_PER_MG;
        }
    }

    private class ApplyInsulin extends IncreaseDecrease {

        private ApplyInsulin(int units) {
            super(Mode.DECREASE);
            this.initialLatencyInMinutes = INSULIN_AFFECT_LATENCY_IN_MINUTES;
            this.steps = units;
            this.stepDurationInMinutes = INSULIN_AFFECT_DURATION_PER_UNIT_IN_MINUTES;
            this.increaseDecreasePerStep = INSULIN_AFFECT_PER_UNIT;
        }
    }

    public SimulationBody(Scheduler scheduler) {
        this.currentBSL = new AtomicInteger(generateRandomBSL());
        setScheduler(scheduler);
    }

    public Scheduler getScheduler() {
        return scheduler;
    }

    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    @Override
    public int getBloodSugarLevel() {
        return currentBSL.intValue();
    }

    @Override
    public void injectInsulin(int units) {
        new ApplyInsulin(units).start();
    }

    private int generateRandomBSL() {
        int minBSL = BloodSugarLevel.OK.getMinimum().getLevel();
        int maxBSL = BloodSugarLevel.OK.getMaximum().getLevel();
        return (int) (minBSL + (Math.random() * (maxBSL - minBSL)));
    }

    @Override
    public void eatOrDrink(Aliment aliment) {
        new ApplySugar(aliment.getSugarInMg()).start();
    }

    public List<Thread> getRunningThreads() {
        return new ArrayList<Thread>(runningThreads);
    }

}
